package io.github.handyang.algorithms.search;

import io.github.handyang.algorithms.base.Stopwatch;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

@Slf4j
public class SearchTest {
    private static final TestResult SMALL_DATA_TEST_RESULT;
    private static final TestResult LARGE_DATA_TEST_RESULT;

    static {
        SMALL_DATA_TEST_RESULT = new TestResult("the", 175, 3106, 685, 0d, 0d);
        LARGE_DATA_TEST_RESULT = new TestResult("the", 4943, 87428, 18435, 0d, 0d);
    }

    private List<String> prepareSmallData() {
        return prepareData("search_test_small_data.txt");
    }

    private List<String> prepareLargeData() {
        return prepareData("search_test_large_data.txt");
    }

    private List<String> prepareData(String fileName) {
        try {
            List<String> res = new ArrayList<>();

            BufferedReader fis = new BufferedReader(new InputStreamReader(this.getClass().getClassLoader().getResourceAsStream(fileName)));
            String line;
            while ((line = fis.readLine()) != null) {
                String[] split = line.split(" ");
                for (String s : split) {
                    if (s.trim().length() == 0) continue;
                    res.add(s);
                }
            }

            return res;
        } catch (IOException e) {
            log.error("", e);
        }

        return Collections.emptyList();
    }

    private TestResult timeTest(SymbolTable<String, Integer> table, List<String> data) {
        TestResult testResult = new TestResult();

        Stopwatch stopwatch = new Stopwatch();
        int distinct = 0, words = 0;

        for (String word : data) {
            words++;
            if (table.contains(word)) {
                table.put(word, table.get(word) + 1);
            } else {
                table.put(word, 1);
                distinct++;
            }
        }

        testResult.setBuildSeconds(stopwatch.elapsedTime());
        String max = "";
        table.put(max, 0);
        for (String word : table.keys()) {
            if (table.get(word) > table.get(max)) {
                max = word;
            }
        }

        testResult.setMaxKey(max);
        testResult.setMaxKeyCount(table.get(max));
        testResult.setWordCount(words);
        testResult.setDistinctWordCount(distinct);
        testResult.setFindMaxSeconds(stopwatch.elapsedTime() - testResult.getBuildSeconds());

        return testResult;
    }

    private void assertTimeTest(SymbolTable<String, Integer> table) {
        List<String> smallData = prepareSmallData();
        List<String> largeData = prepareLargeData();

        TestResult smallTestResult = timeTest(table, smallData);
        assert smallTestResult.equals(SMALL_DATA_TEST_RESULT);

        table.clear();

        TestResult largeTestResult = timeTest(table, largeData);
        assert largeTestResult.equals(LARGE_DATA_TEST_RESULT);

        log.info("table:{}", table.getClass().getSimpleName());
        log.info("smallTestResult:{}", smallTestResult);
        log.info("largeTestResult:{}\n", largeTestResult);
    }

    private void printTable(SymbolTable<String, Integer> table) {
        table.keys().forEach(key -> log.info("key:{}, value:{}", key, table.get(key)));
    }

    private void printLeafDep(TreeNode node, int dep) {
        if (node == null) return;

        if (node.getLeft() == null && node.getRight() == null) {
            log.info("Leaf:{},dep:{}", node.getKey(), dep);
        }

        printLeafDep(node.getLeft(), dep + 1);
        printLeafDep(node.getRight(), dep + 1);
    }

    @Test
    public void testOrderSearch() {
        SymbolTable<String, Integer> table = new OrderSearchST<>();
        assertTimeTest(table);
    }

    @Test
    public void testBinarySearch() {
        SymbolTable<String, Integer> table = new BinarySearchST<>();
        assertTimeTest(table);
    }

    @Test
    public void testBST() {
        BST<String, Integer> table = new BST<>();
        assertTimeTest(table);

//        printLeafDep(table.getRoot(), 0);
    }

    @Test
    public void testRedBlackBST() throws Exception {
        RedBlackBST<String, Integer> table = new RedBlackBST<>();
        assertTimeTest(table);

//        printLeafDep(table.getRoot(), 0);
    }

    @Test
    public void testLinkHashST() throws Exception {
        SymbolTable<String, Integer> table = new LinkHashST<>();
        assertTimeTest(table);
    }
}
